import fs from 'node:fs';
import path from 'node:path';

import {
    merge,
    copyDirectory,
    getPackageMangerName,
    execCommand,
    findParentFile
} from 'mv-common';

import {
    Dependencies,
    MoveModuleDependenciesProps,
    PackageJSON,
    IndexType
} from './type';
import Hooks from './hook';

/**
 * 导出类型
 * **/
export { Dependencies, MoveModuleDependenciesProps, PackageJSON, IndexType };

class MoveModuleDependencies extends Hooks {
    public packageCli: string | void = undefined;
    public rootPath: string = process.cwd();
    public defaultConfig: MoveModuleDependenciesProps = {
        movePath: [],
        dependencies: {
            devDependencies: {},
            dependencies: {}
        },
        coverIgnore: {
            file: []
        }
    };
    public config = this.defaultConfig;

    public async verifyRootPath(searchBasePath: string) {
        this.rootPath = await findParentFile(searchBasePath, 'package.json');
        if (!this.rootPath || !fs.existsSync(this.rootPath)) {
            console.warn(
                'target path is not valid project, ignore dependencies install...'
            );
            return;
        }
        return true;
    }

    public async startWorker(config: Partial<MoveModuleDependenciesProps>) {
        this.config = merge.recursive(this.defaultConfig, config);
        if (!this.config.movePath.length)
            throw new Error('move path can not be null');

        // 移动文件
        await this.moveModule();

        // 校验必要参数
        const pass = await this.verifyRootPath(this.config.movePath[0].dest);
        if (!pass) return pass;
        this.packageCli = getPackageMangerName(this.rootPath) || 'npm';
        return this.installDependencies();
    }

    public async moveModule() {
        console.log('move module start...');
        const modules = this.config.movePath;
        const coverIgnoreFile = this.config.coverIgnore.file;
        for (const item of modules) {
            await copyDirectory(
                item.origin,
                item.dest,
                (_: string, targetPath: string) =>
                    !coverIgnoreFile.find((item) => targetPath.includes(item))
            );
        }
        console.log('move module end...');
    }

    public checkVanner(): Promise<boolean> {
        return new Promise((resolve) => {
            execCommand('vanner')
                .then((res: string) => {
                    resolve(res.startsWith('Usage'));
                })
                .catch(() => {
                    resolve(false);
                });
        });
    }

    public async installDependencies() {
        console.log('install dependencies start...');
        await this.parserDependencies();

        let packageCli = this.packageCli;

        // 检查 vanner 是否安装
        const existsVanner = await this.checkVanner();
        if (existsVanner) packageCli = 'vanner';

        console.log(`package cli is：${packageCli}`);
        await execCommand(`${packageCli} install`, {
            stdio: 'inherit',
            cwd: this.rootPath
        });
        console.log('install dependencies end...');
    }

    public async parserDependencies() {
        const { devDependencies, dependencies } = this.config.dependencies;
        const all: IndexType<string> = {};
        for (const key in devDependencies) {
            if (devDependencies[key] === dependencies[key]) {
                all[key] = devDependencies[key];
                delete dependencies[key];
                delete devDependencies[key];
            }
        }
        const packageFilePath = path.resolve(this.rootPath, 'package.json');
        let packageJSON = JSON.parse(
            fs.readFileSync(packageFilePath, { encoding: 'utf-8' })
        );
        packageJSON.dependencies = {
            ...(packageJSON.dependencies || {}),
            ...dependencies,
            ...all
        };
        packageJSON.devDependencies = {
            ...(packageJSON.devDependencies || {}),
            ...devDependencies,
            ...all
        };

        packageJSON = await this.useAddPackageJSON(packageJSON);
        fs.writeFileSync(
            packageFilePath,
            JSON.stringify(packageJSON, null, 4),
            {
                encoding: 'utf-8'
            }
        );
    }
}

export default MoveModuleDependencies;
